import { PropType, watchPostEffect } from 'vue'
import { defineComponent, ref, computed } from 'vue'
import { Arrayable } from '@vueuse/core'
import { castArray, keyBy } from '../_util/_'
import { useMemo } from '../_util/hooks'
import { normalizeOptions, WillOption, Option } from '../_util/normalizeOptions'

import SelectTrigger from './SelectTrigger'
import OptionList from './OptionList'
import Selector from './Selector'
import useSelectTriggerControl from './hooks/useSelectTriggerControl'

import Icon from '../icon'

const prefixCls = 'x-select'

export default defineComponent({
  name: prefixCls,
  props: {
    value: [String, Number, Array] as PropType<Arrayable<string | number>>,
    options: Array as PropType<WillOption[]>,
    multiple: Boolean,
    disabled: Boolean,
    showArrow: { default: true },
    clearable: Boolean,
    virtualScroll: Boolean,
    placeholder: { default: '请选择' },
    filterable: Boolean,
    filter: Function as PropType<(inputValue: string, option: Option) => Boolean>
  },
  slots: ['default', 'label'],
  emits: ['update:value', 'change', 'select', 'deselect', 'search', 'focus', 'blur'],
  setup(props, { emit, expose, slots }) {
    const containerRef = ref<HTMLElement>()
    const popupWidth = ref(0)
    const inputRef = ref<HTMLInputElement>()
    const popupRef = ref()

    const _options = computed(() => normalizeOptions(props.options ?? []))
    const options = computed(() => {
      const searchVal = search.value
      if (props.filterable && searchVal) {
        const filter = props.filter || ((str: string, option: Option) => option.label.toString().includes(str))
        return _options.value.filter(opt => filter(searchVal, opt))
      }
      return _options.value
    })

    const optionsBy = computed(() => keyBy(props.options, 'value'))
    const values = useMemo(() => castArray(props.value))
    const selected = computed(() => values.value.map(e => optionsBy.value[e]))
    const search = ref('')

    const valuesSet = computed(() => new Set(values.value))

    function setValue(arr) {
      const value = props.multiple ? [...arr] : arr[0]
      console.log(value)

      emit('update:value', value)
      emit('change', value)
    }
    function setSearchValue(text: string) {
      search.value = text
      emit('search', text)
    }

    // ============================= Open =============================
    const opened = ref(false)
    function onToggleOpen(boo?: boolean) {
      if (props.disabled) return
      if (boo === undefined) {
        opened.value = !opened.value
      } else {
        opened.value = boo
      }
    }
    useSelectTriggerControl([containerRef, popupRef], onToggleOpen)

    // ============================= Event =============================
    function onSelect(option: Option) {
      const value = option.value
      if (!props.multiple) {
        values.value = [value]
        emit('select', option.value, option)
      } else {
        const index = values.value.indexOf(option.value)
        if (~index) {
          values.value.splice(index, 1)
          emit('deselect', value, option)
        } else {
          values.value.push(value)
          emit('select', option.value, option)
        }
      }
      search.value = '' // 清空搜索
      const newVal = props.multiple ? [...values.value] : values.value[0]
      emit('update:value', newVal)
      emit('change', newVal, option)

      if (!props.multiple) {
        onToggleOpen(false)
      }
    }
    function onClear() {
      setValue([])
      setSearchValue('')
    }
    function onSearch(text: string) {
      setSearchValue(text)
    }

    function onMenuMousedown(e: MouseEvent) {
      e.preventDefault()
    }

    // ========================== Focus / Blur ==========================
    const focusRef = ref(false)

    function onFocus(e: FocusEvent) {
      if (props.disabled) return
      focusRef.value = true
      // onToggleOpen(true)
      setSearchValue('')
      emit('focus', e)
    }
    function onBlur(e: FocusEvent) {
      focusRef.value = false
      if (props.disabled) return
      setSearchValue('')
      emit('blur', e)
    }

    // ========================== Expose ==========================
    function focus() {
      inputRef.value.focus()
    }
    function blur() {
      inputRef.value.blur()
    }
    expose({
      focus,
      blur
    })

    // ========================== popupWidth ==========================
    watchPostEffect(() => {
      if (!opened.value) return
      if (!containerRef.value) return
      popupWidth.value = containerRef.value.offsetWidth
    })

    return () => {
      const { clearable, showArrow, disabled } = props
      const vals = values.value
      const popup = (
        <OptionList
          prefixCls={prefixCls}
          ref={popupRef}
          values={valuesSet.value}
          options={options.value}
          virtualScroll={props.virtualScroll}
          style='max-height: 500px'
          onSelect={onSelect}
          // todo
          onMousedown={onMenuMousedown}
        >
          {{ label: slots.label }}
        </OptionList>
      )
      const triggerProps = {
        prefixCls,
        popup,
        popupStyle: `width: ${popupWidth.value}px`,
        visible: opened.value,
        disabled: props.disabled,
        filterable: props.filterable
      }
      const cls = [
        prefixCls,
        {
          [`${prefixCls}-multiple`]: props.multiple,
          [`${prefixCls}-single`]: !props.multiple,
          [`${prefixCls}-filterable`]: props.filterable,
          [`${prefixCls}-focus`]: focusRef.value,
          [`${prefixCls}-disabled`]: disabled
        }
      ]

      const clearNode = !disabled && clearable && vals.length ? <Icon class={`${prefixCls}-clear`} onClick={onClear} type='cross' /> : undefined
      const arrowNode = !disabled && !clearNode && showArrow ? <Icon class={`${prefixCls}-arrow`} type='arrow-down' /> : undefined

      return (
        <div ref={containerRef} class={cls}>
          <SelectTrigger {...triggerProps}>
            <Selector
              //
              prefixCls={prefixCls}
              values={selected.value}
              multiple={props.multiple}
              searchValue={search.value}
              filterable={props.filterable}
              showArrow={props.showArrow}
              clearable={props.clearable}
              disabled={props.disabled}
              placeholder={props.placeholder}
              inputRef={inputRef}
              onSelect={onSelect}
              onSearch={onSearch}
              onToggleOpen={onToggleOpen}
              onFocus={onFocus}
              onBlur={onBlur}
            ></Selector>
          </SelectTrigger>
          {clearNode}
          {arrowNode}
        </div>
      )
    }
  }
})
